/* Moduł symulujący dostęp do łącza. */

#include "link.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void unpack_frame( void *frame, size_t *size )
{
    uint16_t *ethertype = (uint16_t*)frame+6;
    if( *ethertype < HWMTU )
        *ethertype -= 3;
    /* Dla specyficznych ramek (ethertype oznacza typ) rozmiaru nie zmieniamy.*/
    *size -= 3;
}

static void memswap( void *buff1, void *buff2, size_t buffsize )
{
    for( uint i = 0; i < buffsize; i++ )
    {
        char tmp = ((char*)buff1)[i];
        ((char*)buff1)[i] = ((char*)buff2)[i];
        ((char*)buff2)[i] = tmp;
    }
}

#define GET_TIMESTAMP( LINK, FRAMENO ) \
    *(tstamp_t*)(LINK->frame_buffer[FRAMENO].content + \
        LINK->frame_buffer[FRAMENO].size - 2)
#define GET_SSID( LINK, FRAMENO ) \
    LINK->frame_buffer[FRAMENO].content[LINK->frame_buffer[FRAMENO].size - 3]

/* funkcje składowe symulacji. Zwracają true jeśli się powiodą i false
 * jeśli nie. */

static bool init_timer( Link *handle )
{
    struct timespec ts;
    if( clock_gettime( DEFAULT_TIMER, &ts ) != 0 )
    {
        perror( "get time" );
        return false;
    }

    handle->timer = ts.tv_nsec / TIME_UNIT; 
    return true;
}

/* Przed wysyłaniem wszystkich ramek, trzeba je posortować po timestampie. */
static bool sort_by_timestamp( Link *handle )
{
    /* Używam sortowania przez wybór. Jest proste, ale nie wymaga tyle
     * kopiowania co bombelkowe. Ponadto ma mniej zmienną złożoność. */
    uint frame_no, min_ts, min_ts_pos;
    for( frame_no = 0; frame_no < handle->ifaces_in_network - 1; frame_no ++ )
    {
        min_ts = 0xffff;
        for( uint i = frame_no; i < handle->ifaces_in_network; i++ )
        {
            if( handle->frame_buffer[i].size == 0 )
                continue;

            tstamp_t ts = GET_TIMESTAMP( handle, i );
            if( ts < min_ts )
            {
                min_ts = ts;
                min_ts_pos = i;
            }
        }
        if( min_ts == 0xffff )
            break;
        if( min_ts_pos != frame_no )
        {
            memswap( handle->frame_buffer + min_ts_pos,
                    handle->frame_buffer + frame_no, sizeof( FrameBuffer ) );
        }
    }

    return true;
}

/* Wyślij ramkę na odpowiednie wyjście.
 * TODO: Na razie tylko wypisuje ramki na wyjście. Powinna usuwać
 * timestamp i SSID i wysyłać do tapa. */
static bool send_frame( Link *handle, int frame_idx )
{
    uint i = 0;
    while( i < handle->ifaces_in_node )
    {
        if( bcmp( handle->outputs[i].adres_mac,
                    handle->frame_buffer[frame_idx].content, 6 ) == 0 )
            break;
        i++;
    }

    if( i < handle->ifaces_in_node &&
            GET_SSID( handle, frame_idx ) == handle->outputs[i].ssid )
    {
        unpack_frame( handle->frame_buffer[frame_idx].content,
                &( handle->frame_buffer[frame_idx].size ) );
        if( write( handle->outputs[i].deskryptor_pliku,
                handle->frame_buffer[frame_idx].content,
                handle->frame_buffer[frame_idx].size ) == -1 )
        {
            perror( "write frame to file" );
            return false;
        }
    }
#ifdef DEBUG
    else
    {
        printf( "dropped: " );
        for( i = 0; i < handle->frame_buffer[frame_idx].size; i += 4 )
            printf( "%x ", *(uint*)(handle->frame_buffer[frame_idx].content + i));
    printf( "\n" );
    }
#endif

    return true;
}

/* Opóźnij wysłanie następnej ramki tak aby naśladował CSMA/CA. */
static bool simulate_intervals( Link *handle, uint frame_idx )
{
    if( frame_idx == 0 )
        handle->itimer = IFS_TIME + handle->frame_buffer[0].size * TSPEED;
    else if( handle->itimer <= GET_TIMESTAMP( handle, frame_idx ) )
        handle->itimer += IFS_TIME + handle->frame_buffer[frame_idx].size *
                                                                TSPEED;
    else
        handle->itimer += DIFS_TIME + handle->frame_buffer[frame_idx].size *
                                                                TSPEED;

    handle->itimer += rand() % MAX_BACKOFF * TIME_SLOT;
    handle->itimer += SIFS_TIME + ACK_BYTES * TSPEED;

    return true;
}

Link *new_link_sim( size_t      ifaces_in_network,
                    size_t      ifaces_in_node,
                    Urzadzenie  *outputs )
{
    Link *result = (Link *) malloc( sizeof( Link ) );
    if( result == NULL )
    {
        fprintf( stderr, "link.c(%d): malloc failed.\n", __LINE__ );
        return NULL;
    }

    result->ifaces_in_network = ifaces_in_network;
    result->ifaces_in_node = ifaces_in_node;
    result->outputs = outputs;

    result->frame_buffer =
        (FrameBuffer*) malloc( sizeof( FrameBuffer ) * ifaces_in_network );
    if( result->frame_buffer == NULL )
    {
        fprintf( stderr, "link.c(%d): malloc failed.\n", __LINE__ );
        free( result );
        return NULL;
    }

    for( int i = 0; i < ifaces_in_network; i++ )
        result->frame_buffer[i].size = 0;

    result->processed_frames = 0;
    result->simulation_time = 0;
    result->simulated_time = 0;
    
    return result;
}

void end_link_sim( Link *handle )
{
    /* TODO */
}

bool link_simulate( Link *handle )
{
    uint i;

    handle->itimer = 0;
    if( !init_timer( handle ) )
        return false;
    if( !sort_by_timestamp( handle ) )
        return false;

    /* symulacja jest pseudolosowa. Wszystkie symulatory w sieci powinny dać
     * możliwie zbliżony wynik, więc ziarno liczymy na podstawie 2 najmniejszych
     * timestampów. */
    srand( GET_TIMESTAMP( handle, 0 ) + ( GET_TIMESTAMP( handle, 1 ) << 16 ) );

    for( i = 0; i < handle->ifaces_in_network; i++ )
    {
        if( handle->frame_buffer[i].size == 0 )
            break;

        send_frame( handle, i );

        simulate_intervals( handle, i );
    }

    for( int i = 0; i < handle->ifaces_in_network; i++ )
        handle->frame_buffer[i].size = 0;

    if( handle->itimer == 0 )
        return true;

    struct timespec ts;
    if( clock_gettime( DEFAULT_TIMER, &ts ) != 0 )
    {
        perror( "gettime" );
        return false;
    }
    
    uint cputime;
    if( ts.tv_nsec < handle->timer )
        cputime = (ts.tv_nsec - handle->timer + 1000000) / 1000;
    else
        cputime = (ts.tv_nsec - handle->timer ) / 1000;

    handle->processed_frames += i;
    handle->simulation_time += cputime;
    handle->simulated_time += handle->itimer;

    return true;
}

